#ifndef __thinvm_icache__
#define __thinvm_icache__

#include "thinvm.h"
#include "mmu.h"

namespace thinvm
{
	enum ic_status_t
	{
		IcsHit,
		IcsMiss,
		IcsConfus,
	};

	struct instruction_cache_t
	{
		enum { SLOTS = 0x1000 };
		struct cache_entry_t
		{
			bx_uint32_t			addr;
			bx_instruction_t	inst;
			cache_entry_t() : addr(0) {};
		};

		cache_entry_t	_items[SLOTS];

#ifdef ICACHE_DEBUG
		size_t			_i_fetch;
		size_t			_i_hit;
		size_t			_i_confus;
#endif

		instruction_cache_t()
#ifdef ICACHE_DEBUG
			: _i_fetch(0), _i_hit(0), _i_confus(0)
#endif
		{
			clear();
		}
#ifdef ICACHE_DEBUG
		~instruction_cache_t()
		{
			printf( "ICache : %d/%d, %d\n", _i_hit, _i_fetch, _i_confus );
		}
#endif
		inline void clear()
		{
			memset( _items, 0, sizeof(_items) );
		}
		inline ic_status_t get( bx_uint32_t addr, bx_instruction_t ** ppi, uint32_t * confus_addr = 0 )
		{
#ifdef ICACHE_DEBUG
			++ _i_fetch;
#endif
			size_t index = addr % SLOTS;
			cache_entry_t & item = _items[index];
			if( !item.inst.ilen() )
			{
				item.addr = addr;
				*ppi = &item.inst;
				return IcsMiss;
			}
			if( item.addr == addr )
			{
#ifdef ICACHE_DEBUG
				++ _i_hit;
#endif
				*ppi = &item.inst;
				return IcsHit;
			}
#ifdef ICACHE_DEBUG
			++ _i_confus;
#endif
			if( confus_addr ) *confus_addr = item.addr;
			item.addr = addr;
			memset( &item.inst, 0, sizeof(item.inst) );
			*ppi = &item.inst;
			return IcsConfus;
		}

		void invalid_in_page( mmu_page_desc_t * desc, bx_uint32_t base, bx_uint32_t offset, bx_uint32_t off_end )
		{
			for( ; desc->icic && offset < off_end; ++ offset )
			{
				size_t addr = base + offset;
				size_t index = addr % SLOTS;

				cache_entry_t & item = _items[index];

				if( !item.inst.ilen() ) 
					continue;

				if( item.addr != addr )
					continue;

				item.addr = 0;
				memset( &item.inst, 0, sizeof(item.inst) );
				-- desc->icic;
			}
		}

		virtual void on_in_cache_instruction_invalid( bx_uint32_t addr, bx_instruction_t * inst  ) {};
	};
};

#endif